package oop.ex2.compiler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.text.html.HTMLDocument.HTMLReader.IsindexAction;

import oop.ex2.enums.CommentEnum;
import oop.ex2.enums.GeneralEnum;
import oop.ex2.enums.IfWhileEnum;
import oop.ex2.enums.MethodEnum;
import oop.ex2.enums.VariableEnum;
import oop.ex2.enums.VariableValueEnum;
import oop.ex2.objects.Method;
import oop.ex2.objects.Variable;

public class Compiler {

    private ArrayList<String> fileContents;

    private HashMap<String, Variable> variables = new HashMap<String, Variable>();
    private HashMap<String, Method> methods = new HashMap<String, Method>();
    private String validType[] = { "String", "int", "double", "boolean", "char" };

    public Compiler(ArrayList<String> fileContents) {
        this.fileContents = fileContents;
    }

    public void checkCode() throws IllegalCodeException {
        checkCommentPatterns();
        checkGeneralPatterns();
        checkMethodsDeclaration();
        for (String line : fileContents) {
            checkVariables(line, false);
        }
        checkIfWhile();
        checkRestOfCode();
    }

    private void checkIfWhile() throws MethodException, IfWhileException {
        String condition = "";
        boolean isInsideIfWhile = false;
        for (int i = 0; i < fileContents.size(); i++) {
            boolean isIfWhileLine = false;
            if (isInsideIfWhile) {
                if (fileContents.get(i).endsWith("}")) {
                    isInsideIfWhile = false;
                    fileContents.remove(i);
                    break;
                }
            }
            for (IfWhileEnum patternEnum : IfWhileEnum.values()) {
                Matcher ifWhileMatcher = patternEnum.getEnumPattern().matcher(fileContents.get(i));
                while (ifWhileMatcher.find()) {
                    condition = ifWhileMatcher.group(2);
                    if (!checkVariableValue(condition, "boolean")) {
                        throw new IfWhileException("Illegal argument in if/while statement. Line " + (i + 1)
                                + ".");
                    }
                    fileContents.remove(i);
                    isIfWhileLine = true;
                    isInsideIfWhile = true;
                }
            }
            if (isIfWhileLine) {
                i--;
            }
        }
    }

    private void checkMethodsDeclaration() throws MethodException, VariablesException {
        String type = "";
        String name = "";
        boolean validArgument;
        boolean isInsideMethod = false;
        for (int i = 0; i < fileContents.size(); i++) {
            boolean validLine = false;
            ArrayList<String> argumentsType = new ArrayList<String>();
            for (MethodEnum method : MethodEnum.values()) {
                Matcher methodMatcher = method.getEnumPattern().matcher(fileContents.get(i));
                while (methodMatcher.find()) {
                    if (fileContents.get(i).endsWith("{")) {
                        isInsideMethod = true;
                    }
                    type = methodMatcher.group(1);
                    name = methodMatcher.group(2);
                    String[] arguments = methodMatcher.group(3).split(",");
                    if (methods.containsKey(name)) {
                        throw new MethodException("Method name \'" + name + "\' already exists. Line " + (i + 1));
                    }
                    for (String argument : arguments) {
                        if (argument.contains("=")) {
                            throw new MethodException("Method \'" + name
                                    + "\' contains a value assignment in the arguments. Line " + (i + 1));
                        }

                        argument = argument.replace("(", "");

                        argument = argument.replace(")", "");
                        argument = argument.trim();
                        String[] oneArgument = argument.split("\\s");
                        String argumentTypeString = oneArgument[0];
                        String argumentNameString = "";
                        argumentsType.add(argument.split(" ")[0]);

                        if (oneArgument.length > 1) {
                            argumentNameString = oneArgument[1];
                        }
                        if (variables.containsKey(argumentNameString)) {
                            if (variables.get(argumentNameString).isField() == true) {
                                variables.put(argumentNameString, new Variable(argumentTypeString,
                                        argumentNameString, "", false, true));
                            } else {
                                throw new VariablesException("Variable " + argumentNameString
                                        + " already exists.");
                            }
                        } else {
                            variables.put(argumentNameString, new Variable(argumentTypeString,
                                    argumentNameString, "", false, true));
                        }

                    }
                    for (String argument : argumentsType) {
                        if (!argument.isEmpty()) {
                            validArgument = false;
                            for (String currentValidType : validType) {
                                if (argument.equals(currentValidType)) {
                                    validArgument = true;
                                }
                            }
                            if (!validArgument) {
                                throw new MethodException("Illegal argument \'" + argument + "\' in method \'"
                                        + name + "\'. Line " + (i + 1));
                            }
                        }
                    }
                    validLine = true;
                }
            }
            if (isInsideMethod) {
                if (fileContents.get(i).endsWith("}")) {
                    isInsideMethod = false;
                    fileContents.remove(i);
                    i--;
                    continue;
                }
                Pattern returnPattern = Pattern.compile("\\s*(return)(\\s+\\S+)*\\s*;");
                Matcher returnMatcher = returnPattern.matcher(fileContents.get(i));
                if (returnMatcher.find()) {
                    String returnString = "";

                    if (returnMatcher.group(2) != null) {
                        returnString = returnMatcher.group(2);
                        returnString = returnString.trim();
                    }
                    if ((returnString.isEmpty()) || (checkVariableValue(returnString, type))) {
                        fileContents.remove(i);
                        i--;
                        continue;
                    }
                }

            }
            if (validLine) {
                fileContents.remove(i);
                i--;
                methods.put(name, new Method(name, type, argumentsType));

            }
        }
    }

    private void checkRestOfCode() throws IllegalCodeException {
        for (String line : fileContents) {
            if (line.endsWith("\\checked")) {
                continue;
            }
            if (!checkVariables(line, false)) {
                System.out.println(line);
                throw new IllegalCodeException("Unknown Line " + (fileContents.indexOf(line) + 1));
            }
        }
    }

    private boolean checkMethods(String method, String name) throws MethodException {
        String returned = checkMethodsValue(method, name);
        if (returned.equals("false")) {
            return false;
        }
        for (String type : validType) {
            if (name.equals(type)) {
                if (returned.equals(name)) {
                    return true;
                }
                return false;
            }
        }
        if (variables.get(name).getType().equals(returned)) {
            return true;
        }
        return false;
    }

    private String checkMethodsValue(String method, String name) throws MethodException {
        return name;
        // String value = "";
        // String argumentString = "";
        // String[] arguments = null;
        // Matcher parentesisMatcher =
        // MethodEnum.PARENTHESIS.getEnumPattern().matcher(method);
        // if (parentesisMatcher.find()) {
        // name = method.substring(0, parentesisMatcher.start());
        // value = method.substring(parentesisMatcher.start() + 1,
        // parentesisMatcher.end() - 1);
        // argumentString = checkMethodsValue(value, name);
        // System.out.println(argumentString + " Hogb");
        // } else {
        // arguments = method.split(",");
        // }
        // System.out.println(name);
        // if (!methods.containsKey(name)) {
        // throw new MethodException("Unknown argumet \'" + name + "\'.");
        // }
        // ArrayList<String> argumentsType =
        // methods.get(name).getArgumentsType();
        // System.out.println(value);
        // if (arguments.length != argumentsType.size()) {
        // System.out.println(arguments.length + " " + argumentsType.size());
        // throw new MethodException("Arguments number mismatch for method \'" +
        // name + "\'.");
        // }
        // for (int i = 0; i < arguments.length; i++) {
        // String argumentTypeString = checkMethodsValue(arguments[i], name);
        // if (!argumentTypeString.equals(argumentsType.get(i))) {
        // throw new MethodException("Argument type mismatch.");
        // }
        // }
        // String returnType = methods.get(name).getReturnType();
        // // if(checkMethodsValue(value))
        // if (value.isEmpty()) {
        // return returnType;
        // } else {
        // return name;
        // }
    }

    private boolean checkVariables(String line, boolean isInsideMethod) throws VariablesException,
            MethodException {
        if (line.isEmpty()) {
            return true;
        }
        String type = "";
        String name = "";
        String value = "";
        boolean isField = !isInsideMethod;
        boolean isValidVariableLine = false;

        for (VariableEnum variable : VariableEnum.values()) {
            Matcher lineMatcher = variable.getEnumPattern().matcher(line);

            while (lineMatcher.find()) {
                if (variable.equals(VariableEnum.FINAL)) {
                    Matcher insertionMatcher = VariableEnum.INSERTION.getEnumPattern().matcher(line);
                    if (!insertionMatcher.find()) {
                        throw new VariablesException("final variable must be assigned a value;");
                    } else {
                        continue;
                    }
                } else if (variable.equals(VariableEnum.INSERTION)) {
                    name = lineMatcher.group(1);
                    value = lineMatcher.group(2);
                    value = clearValue(value);
                    if (!variables.containsKey(name)) {
                        throw new VariablesException("Could not assign value. Variable '" + name + "' not found.");
                    } else {
                        variables.get(name).setValue(value);
                        continue;
                    }
                }
                type = lineMatcher.group(1);
                name = lineMatcher.group(2);
                value = lineMatcher.group(3);
                if (variables.containsKey(name) && !variables.get(name).isField()) {
                    throw new VariablesException("Variable \'" + name + "\' Already exists. Line "
                            + (fileContents.indexOf(line) + 1));
                }
                if (!value.isEmpty()) { // Value exists.
                    value = value.replace(";", "");
                    value = value.replace("=", "");
                    value = value.trim();
                    if (!checkVariableValue(value, type)) {
                        throw new VariablesException("Incorrect value type to variable \'" + name + "\'. Line "
                                + (fileContents.indexOf(line) + 1));
                    }
                }

                variables.put(name, new Variable(type, name, value, isField, false));
                isValidVariableLine = true;
            }
        }
        return isValidVariableLine;
    }

    private String clearValue(String value) {
        value = value.replace(";", "");
        value = value.replace("=", "");
        value = value.trim();
        return value;
    }

    private boolean checkVariableValue(String value, String type) throws MethodException {
        boolean validValue = false;
        value = clearValue(value);
        Matcher valueMatcher = VariableValueEnum.valueOf(type.toUpperCase()).getEnumPattern().matcher(value);
        Matcher parenthesisMatcher = MethodEnum.PARENTHESIS.getEnumPattern().matcher(value);

        if (!valueMatcher.matches()) {
            if (parenthesisMatcher.find()) {

                checkMethods(value, type); // Change to validValue =
                                           // checkMethods()
                validValue = true;
            } else if (variables.containsKey(value)) {
                Variable valueVariable = variables.get(value);
                if (valueVariable.getType().equals(type) && !valueVariable.getValue().isEmpty()) {
                    validValue = true;
                }
            } else {
                Matcher variableMatcher = VariableEnum.valueOf(type.toUpperCase()).getEnumPattern()
                        .matcher(value);
                if (variableMatcher.find()) {
                    validValue = true;
                }
            }
        } else {
            validValue = true;
        }
        return validValue;
    }

    private void checkCommentPatterns() {
        boolean isInsideComment = false;
        for (int i = 0; i < fileContents.size(); i++) {
            for (CommentEnum comment : CommentEnum.values()) {
                Matcher commentMatcher = comment.getEnumPattern().matcher(fileContents.get(i));
                if (isInsideComment) {
                    fileContents.set(i, "");
                }
                while (commentMatcher.find()) {
                    if (comment.equals(CommentEnum.MULTI_LINE_START)) {
                        isInsideComment = true;
                    } else {
                        isInsideComment = false;
                    }
                    fileContents.set(i, fileContents.get(i).replace(commentMatcher.group(), " "));
                }
            }
        }
    }

    private void checkGeneralPatterns() throws GeneralPatternException {
        for (String line : fileContents) {
            boolean validLine = false;
            for (GeneralEnum general : GeneralEnum.values()) {
                if (line.isEmpty()) {
                    validLine = true;
                }
                Matcher generalMatcher = general.getEnumPattern().matcher(line);
                while (generalMatcher.find()) {
                    validLine = true;
                    break;
                }
            }
            if (!validLine) {
                throw new GeneralPatternException("Line " + (fileContents.indexOf(line) + 1)
                        + " Does not end with a legal character.");
            }
        }
    }
}
